T
Taste Map Engine
0 Total Recommendations
0 Active Queue
0 Consumed & Reviewed
10/10 Highest Rating
Resource
Rationale & Review
Actions
Loading knowledge base...
Push to Queue
Log Review
Video Preview
\n\n\n```\n\nThe point: this is the **shape**. The actual artifact is content. The scaffold is 50 lines of structure, not 500 lines of code to copy.\n\n---\n\n## 7 ยท Design tokens (the minimum useful set)\n\n```css\n:root {\n /* Surfaces โ€” TRUE off-white, not the cream default. Pick a hue. */\n --bg: oklch(0.985 0.004 60); /* true off-white with brand tint */\n --bg-elevated: oklch(1 0 0);\n --bg-subtle: oklch(0.965 0.006 60);\n --bg-strong: oklch(0.94 0.008 60);\n\n --border: oklch(0.90 0.005 60);\n --border-strong: oklch(0.80 0.008 60);\n\n --text: oklch(0.18 0.005 60);\n --text-dim: oklch(0.42 0.006 60);\n --text-faint: oklch(0.58 0.006 60);\n\n /* Committed accent โ€” one color, used deliberately. */\n --accent: oklch(0.50 0.16 50);\n --accent-strong: oklch(0.40 0.16 45);\n --accent-soft: oklch(0.92 0.045 50);\n --accent-wash: oklch(0.96 0.025 50);\n\n /* Semantic palette (only as needed by content) */\n --liberal: oklch(0.52 0.16 250);\n --conservative: oklch(0.50 0.16 30);\n --success: oklch(0.52 0.12 145);\n --danger: oklch(0.50 0.16 25);\n\n /* Spacing โ€” 8px grid */\n --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;\n --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 64px;\n --s-9: 96px; --s-10: 128px;\n\n /* Radius, motion */\n --radius-sm: 6px; --radius: 10px; --radius-lg: 16px;\n --ease: cubic-bezier(0.16, 1, 0.3, 1);\n --t-fast: 150ms; --t-base: 250ms; --t-slow: 450ms;\n\n /* Fonts โ€” pick from the non-banned list */\n --font-display: 'Spectral', 'Iowan Old Style', Charter, Georgia, serif;\n --font-body: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;\n --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace;\n}\n\n[data-theme=\"dark\"] {\n --bg: oklch(0.14 0.004 60);\n --bg-elevated: oklch(0.18 0.005 60);\n --bg-subtle: oklch(0.21 0.005 60);\n --bg-strong: oklch(0.25 0.006 60);\n --border: oklch(0.28 0.005 60);\n --border-strong: oklch(0.36 0.006 60);\n --text: oklch(0.95 0.004 60);\n --text-dim: oklch(0.72 0.006 60);\n --text-faint: oklch(0.55 0.006 60);\n --accent: oklch(0.72 0.16 60);\n --accent-strong: oklch(0.80 0.16 60);\n --accent-soft: oklch(0.30 0.06 50);\n --accent-wash: oklch(0.24 0.04 50);\n}\n```\n\n**The token name matters.** `--bg`, `--text`, `--accent` are semantic. `--font-16`, `--color-1` are not. Name by role, not value.\n\n---\n\n## 8 ยท The interactive primitives\n\nThese are the building blocks. Use a variety โ€” no two consecutive sections should use the same one.\n\n| Primitive | Best for |\n|---|---|\n| **Slider + paired panels** | Openness-type traits, spectrum comparisons. |\n| **Numbered list + detail panel** | Enumerable taxonomies (foundations, types, principles). One active, others at rest. |\n| **Horizontal channels with live readout** | The \"equalizer\" โ€” multiple values that change together when one input moves. |\n| **Animated multi-step sequence** | Time-evolving processes (decay, growth, iteration). Play/reset. |\n| **Choice cards (asymmetric, not 3-grid)** | Two-option predictions, before/after, paired scenarios. |\n| **Pull-quote with attributed source** | Memorable sentences with provenance. |\n| **Divided list with marker bullets** | Key terms, takeaways. No boxes. |\n| **Inline accordion** | A definition that earns its expansion. Use sparingly โ€” one per artifact. |\n| **Scroll-triggered reveal** | Section enters as you scroll. Enhance, don't gate. |\n| **Hover-driven linking** | Distill.pub pattern: hover a term, the visual changes. Not click โ€” hover. |\n\n**Forbidden primitives:**\n- 3-up identical card grid (the SaaS feature row).\n- 4-up identical icon-card grid.\n- Hero metric block (big number + label + gradient).\n- Single CTA button above the fold with a gradient background.\n\n---\n\n## 9 ยท Critical bug guards (carry-over)\n\n1. **Zero CDN scripts.** Everything inline. Fonts are progressive enhancement. The file must open via `file:///` on a plane.\n2. **Defensive glossary key matching.** Always: `g.term || g.name`, `g.definition || g.b2_def`. If the extraction sheet renames a key, the page must not crash.\n3. **Click-outside-to-close drawer.** Excluding term links and the trigger button itself.\n4. **localStorage wrap in try/catch.** Private-browsing mode will throw.\n5. **All state changes go through a Store.** No scattered DOM mutations. Future-you will thank present-you.\n6. **Theme toggle redraws SVG charts.** Charts that read color from CSS variables must be re-rendered after theme change.\n\n---\n\n## 10 ยท Polish checklist (run before shipping)\n\n- [ ] Font choice is not on the reflex-reject list. Pair is on a contrast axis.\n- [ ] Body bg is not the cream/sand default. A color strategy is named.\n- [ ] No side-stripe borders anywhere. No gradient text. No ghost-cards. No identical card grids.\n- [ ] No kicker-above-every-heading. No numbered markers on every section.\n- [ ] No animated `` elements. No layout-property transitions.\n- [ ] No bounce/elastic easing. Reduced-motion fallback is present and works.\n- [ ] Body text โ‰ฅ 4.5:1 against bg. Large text โ‰ฅ 3:1.\n- [ ] Two consecutive sections don't use the same visual primitive.\n- [ ] Every section has both prose AND a tinkerable visual.\n- [ ] Active recall break every 2-3 sections.\n- [ ] First section is an interest-curve hook (predict/poll/simulate before reading).\n- [ ] Keyboard: `/` opens glossary, `Esc` closes it.\n- [ ] Scroll position restored on reload. Theme persisted. Checklist persisted.\n- [ ] No console errors on load. No 404s on fonts (with system fallbacks they shouldn't matter, but check).\n- [ ] Opens via double-click from `/home/mahmud/visual-learn-artifacts/`.\n- [ ] Print stylesheet hides nav, drawer, expands recall answers, includes glossary appendix.\n- [ ] First-order reflex check: can someone guess the topic + theme + palette from category alone? If yes, rework.\n- [ ] Second-order reflex check: can someone guess the aesthetic family from category + anti-references? If yes, rework.\n\n---\n\n## 11 ยท Design philosophy (the \"why\" for future agents)\n\nThree influences, in order of how much the artifact should feel like them:\n\n**Bret Victor & Nicky Case (Explorable Explanations).** Prose is passive; exploration is active. The reader manipulates, observes, *enacts* the concept. If you want to learn game theory, you play a game.\n\n**Andy Matuschak & Michael Nielsen (The Mnemonic Medium).** Reading is forgetting. Embed active recall into the artifact. The reader chooses to remember; the medium supports the choice.\n\n**Distill.pub (Interactive Journals).** Text and visual are the same object. Hover a term, the visual changes. Sticky context + detail overlays. Aligned text structure to visual layout lowers extraneous load.\n\nWhat visual-learn does that none of them do: **all three in one offline, double-clickable file**. That's the bar.\n\n---\n\n## 12 ยท The \"dumb\" decisions, kept honest\n\nThe full lessons-learnt log is below. New ones to add based on v17:\n\n**11. Prescribed the reflex-reject fonts as defaults.**\n- *The mistake:* v10โ€“v16 shipped with `Newsreader + Inter + JetBrains Mono` as the prescribed font stack.\n- *Why it was dumb:* Both Newsreader and Inter are on the impeccable reflex-reject list. Prescribing them made every generated artifact visually identical to every other AI-generated landing page in 2026.\n- *The fix:* v17 removed the prescription. Replaced with a voice-first font selection procedure. The new defaults (Spectral + Geist) are starting points, not mandates.\n\n**12. The cream/sand body default.**\n- *The mistake:* v10โ€“v16 shipped with `--bg: oklch(0.99 0.003 40)` โ€” a warm off-white with hue 40, chroma 0.003.\n- *Why it was dumb:* That exact band (L 0.84-0.97, C < 0.06, hue 40-100) is the saturated AI default of 2026. Every generator picks it. It reads as cream/sand/paper/parchment regardless of what the variable is named.\n- *The fix:* v17 bans this default outright. Requires a named color strategy (restrained / committed / full / drenched) before any tokens are written.\n\n**13. The side-stripe recall block.**\n- *The mistake:* `.recall-block { border-left: 4px solid var(--accent); }` was the prescribed callout pattern.\n- *Why it was dumb:* Side-stripe borders > 1px as colored accents on callouts are on the impeccable absolute-ban list. They read as \"I don't know how to make a callout, so I'll just put a stripe down the side.\"\n- *The fix:* v17 rewrites recall as a numbered editorial block (large number + label + question + reveal). Full borders or none. No stripes.\n\n**14. The 470-line scaffold in a skill file.**\n- *The mistake:* v10โ€“v16 embedded a full HTML scaffold in the skill markdown itself.\n- *Why it was dumb:* Skills are principles, not implementation. Future agents cargo-culted the scaffold instead of thinking about the content. Artifacts looked identical regardless of topic.\n- *The fix:* v17 keeps the scaffold to ~50 lines showing the *shape*. The actual implementation lives in the content, not the boilerplate.\n\n**15. Mono as default UI label font.**\n- *The mistake:* Used `font-family: var(--font-mono)` for `.panel-title` style labels everywhere.\n- *Why it was dumb:* Mono is on the brand ban list when used as \"technical\" shorthand. If the artifact isn't about code, it reads as costume.\n- *The fix:* Mono reserved for actual code, numbers, percentages, data labels. UI labels use the body font at smaller size with letter-spacing, or a small-caps variant.\n\n(Previous mistakes 1โ€“10 retained from v16: ESM CORS trap, SM-2 bloat, narrow screen margin, JSON key fragility, static decorative cards, missing recall, JS scroll handlers, CDN dependency, no hook, random spacing.)\n\n---\n\n## 13 ยท Output rule\n\nGenerate the file at `/home/mahmud/visual-learn-artifacts/.html`. No scaffolding explanation in the response. The file IS the response.\n"; let data = []; let currentTab = 'active'; let viewMode = 'list'; // Initial fallback dataset from system resources if offline/no server const DEFAULT_FALLBACK_DATA = [ { id: '1', video_title: 'Without Conscience: The Disturbing World of the Psychopaths Among Us', creator: 'Robert D. Hare, PhD', video_url: 'https://www.amazon.com/Without-Conscience-Disturbing-World-Psychopaths/dp/1572304510', why_this: 'Foundational primary research on psychopathy by Dr. Robert Hare. Directly feeds pwr-shadow and connects to Machiavelli/Greene interest.', verified: '2026-07-26', status: 'active', user_rating: 'unset', dedup_key: 'pwr-hare-without-conscience', consumed_date: 'unset', synergy_bundle_id: 'unset' }, { id: '2', video_title: 'Pre-Suasion: The Power of Persuasion', creator: 'Robert Cialdini (RSA)', video_url: 'https://www.youtube.com/watch?v=_hem3-8XbEI', why_this: 'Cialdini advanced work beyond the 7 principles โ€” what you do BEFORE the message to win minds.', verified: '2026-07-24', status: 'consumed', user_rating: '9/10', user_review: 'already read, loved it. notes in obsidian.', dedup_key: 'persu-cialdini-presuasion', consumed_date: '2026-07-24', synergy_bundle_id: 'unset' }, { id: '3', video_title: 'Sheldon Solomon: Death and Meaning | Lex Fridman Podcast #117', creator: 'Lex Fridman', video_url: 'https://www.youtube.com/watch?v=qfKyNxfyWbo', why_this: 'TMT co-developer on the record. 2:48 deep-dive on Becker Denial of Death lineage, mortality-salience effects on worldview.', verified: '2026-07-22', status: 'consumed', user_rating: '9/10', user_review: 'ุงู„ุงู‚ุชุฑุงุญ ุฏุง ูƒุงู† ุฃูƒุชุฑ ู…ู† ุฑุงุฆุน ุงู†ุง ูƒู†ุช ุงูˆู„ ู…ุฑุฉ ุงู‚ุฑุงุก ููŠ ุงู„ู…ูˆุช ุจูˆุฌู‡ุฉ ุงู„ู†ุธุฑ ุฏูŠ', dedup_key: 'tmt-solomon-lex117', consumed_date: '2026-07-22', synergy_bundle_id: 'unset' }, { id: '4', video_title: 'Thinking, Fast and Slow', creator: 'Daniel Kahneman', video_url: 'https://www.youtube.com/watch?v=CjVQJdIrDJ0', why_this: 'Nobel Prize-winner on his own research: System 1/2, prospect theory, loss aversion.', verified: '2026-07-24', status: 'consumed', user_rating: '10/10', user_review: 'ุงู†ุง ุจุงู„ูุนู„ ู‚ุฑุงุช ุงู„ูƒุชุงุจ ูˆูƒุงู† ุฒูŠ ุงู„ูู„', dedup_key: 'kahneman-tfas-google', consumed_date: '2026-07-24', synergy_bundle_id: 'unset' }, { id: '5', video_title: 'ุถุจุท ุงู„ู†ูุณ โ€” ู…ุญุงุถุฑุฉ (Self-Control Lecture)', creator: 'ุงู„ุดูŠุฎ ู…ุญู…ุฏ ุตุงู„ุญ ุงู„ู…ู†ุฌุฏ', video_url: 'https://www.youtube.com/watch?v=gUukO6oMHvE', why_this: 'Pure khutbah/lecture by Al-Munajjid on controlling the nafs โ€” directly on tazkiyah.', verified: '2026-07-25', status: 'consumed', user_rating: 'Positive', user_review: 'ุงู„ุญู…ุฏ ู„ู„ู‡ ุฃู†ุง ุนู†ุฏูŠ ุญุชุฉ ุถุจุท ุงู„ู†ูุณ ุฏูŠ ู…ูˆุฌุฏุฉ', dedup_key: 'taz-almunajjid-dabt-nafs', consumed_date: '2026-07-25', synergy_bundle_id: 'unset' } ]; async function fetchRecs() { try { const res = await fetch('/recommendations/list'); if (res.ok) { const json = await res.json(); data = json.recommendations || []; localStorage.setItem('taste_recs_db', JSON.stringify(data)); updateUI(); return; } } catch (err) { console.warn('Backend API unavailable. Switching to Local Storage / Fallback mode.'); } // Fallback to local storage or defaults const local = localStorage.getItem('taste_recs_db'); if (local) { try { data = JSON.parse(local); } catch(e) { data = DEFAULT_FALLBACK_DATA; } } else { data = DEFAULT_FALLBACK_DATA; } updateUI(); } function setViewMode(mode) { viewMode = mode; document.getElementById('btn-view-list').classList.toggle('active', mode === 'list'); document.getElementById('btn-view-cards').classList.toggle('active', mode === 'cards'); const body = document.getElementById('list-body'); body.className = mode === 'cards' ? 'cards-grid' : 'list-view'; updateUI(); } function updateMetrics() { const total = data.length; const active = data.filter(r => r.status === 'active').length; const consumed = data.filter(r => r.status === 'consumed').length; const rejected = data.filter(r => r.status === 'rejected').length; document.getElementById('metric-total').textContent = total; document.getElementById('metric-active').textContent = active; document.getElementById('metric-consumed').textContent = consumed; document.getElementById('count-active').textContent = active; document.getElementById('count-consumed').textContent = consumed; document.getElementById('count-rejected').textContent = rejected; document.getElementById('count-all').textContent = total; } function updateUI() { updateMetrics(); if (document.startViewTransition) { document.startViewTransition(() => render()); } else { render(); } } function render() { const q = document.getElementById('search').value.toLowerCase().trim(); const sortVal = document.getElementById('sort-select').value; let filtered = currentTab === 'all' ? [...data] : data.filter(r => r.status === currentTab); if (q) { filtered = filtered.filter(r => (r.video_title || '').toLowerCase().includes(q) || (r.creator || '').toLowerCase().includes(q) || (r.why_this || '').toLowerCase().includes(q) ); } // Sorting if (sortVal === 'rating') { filtered.sort((a, b) => (b.user_rating || '').localeCompare(a.user_rating || '')); } else if (sortVal === 'title') { filtered.sort((a, b) => (a.video_title || '').localeCompare(b.video_title || '')); } else if (sortVal === 'creator') { filtered.sort((a, b) => (a.creator || '').localeCompare(b.creator || '')); } else { // Newest / default filtered.sort((a, b) => (b.verified || '').localeCompare(a.verified || '')); } const body = document.getElementById('list-body'); const rowHeader = document.getElementById('row-header'); if (currentTab === 'system') { body.style.display = 'block'; rowHeader.style.display = 'none'; body.className = 'list-view'; body.innerHTML = '
' + '
AI Recommendation Prompt Hub
' + '
' + 'Generate a comprehensive Mega-Prompt containing your 37KB Taste Map Profile AND your entire recommendation database (Active, Consumed, and Rejected).

' + 'Paste directly into ChatGPT, Claude, or Gemini for hyper-personalized recommendations.' + '
' + '
' + '' + '' + '
' + '
'; return; } if (viewMode === 'cards') { body.className = 'cards-grid'; rowHeader.style.display = 'none'; } else { body.className = 'list-view'; rowHeader.style.display = 'grid'; } if (filtered.length === 0) { body.innerHTML = '
No entries found matching your filter.
'; return; } body.innerHTML = filtered.map(item => { const badgeClass = 'badge-' + (item.status || 'active'); const ratingStr = item.user_rating && item.user_rating !== 'unset' ? `โ˜… ${item.user_rating}` : ''; const metaLine = [ item.dedup_key && `id: ${item.dedup_key}`, ratingStr ].filter(Boolean).join(' โ€ข '); const ytMatch = (item.video_url || '').match(/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([a-zA-Z0-9_-]{11})/); const hasYtPreview = Boolean(ytMatch); if (viewMode === 'cards') { return `
${item.status} ${escapeHtml(item.verified || '')}
${escapeHtml(item.video_title)}
${escapeHtml(item.creator || 'Unknown')}
${escapeHtml(item.why_this || 'No rationale provided')}
${item.user_review ? `
"${escapeHtml(item.user_review)}"
` : ''}
${escapeHtml(metaLine)}
${hasYtPreview ? `` : ''} โ†— ${item.status === 'active' ? ` ` : ` `}
`; } return `
${item.status} ${escapeHtml(item.video_title)}
${escapeHtml(item.creator || 'Unknown')} โ€ข ${escapeHtml(item.verified || '')}
${escapeHtml(item.why_this || 'No rationale provided')}
${metaLine ? `
${escapeHtml(metaLine)}
` : ''} ${item.user_review ? `
"${escapeHtml(item.user_review)}"
` : ''}
${hasYtPreview ? `` : ''} โ†— ${item.status === 'active' ? ` ` : ` `}
`; }).join(''); } function setTab(tab, el) { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); el.classList.add('active'); currentTab = tab; updateUI(); } function handleSearch() { updateUI(); } function openPushModal() { document.querySelectorAll('#modal-push .form-input, #modal-push .form-textarea').forEach(i => i.value = ''); document.getElementById('modal-push').showModal(); } function openReview(id, targetStatus) { const item = data.find(i => i.id === id || String(i.id) === String(id)); if (!item) return; document.getElementById('review-id').value = item.id; document.getElementById('review-status').value = targetStatus; const titleEl = document.getElementById('review-title'); if (targetStatus === 'consumed') titleEl.textContent = 'Consume & Review Entry'; else if (targetStatus === 'rejected') titleEl.textContent = 'Reject Entry'; else titleEl.textContent = 'Edit Review'; document.getElementById('review-rating').value = item.user_rating !== 'unset' ? (item.user_rating || '') : ''; document.getElementById('review-notes').value = item.user_review || ''; document.getElementById('modal-review').showModal(); } function openPreview(videoId, title) { document.getElementById('preview-title').textContent = title; const container = document.getElementById('preview-container'); container.innerHTML = ``; document.getElementById('modal-preview').showModal(); } function closeModal(id) { const dialog = document.getElementById(id); if (dialog) { dialog.close(); if (id === 'modal-preview') { document.getElementById('preview-container').innerHTML = ''; } } } async function submitPush() { const payload = { id: 'id-' + Date.now(), video_title: document.getElementById('push-title').value.trim(), creator: document.getElementById('push-creator').value.trim(), video_url: document.getElementById('push-url').value.trim(), why_this: document.getElementById('push-why').value.trim(), dedup_key: document.getElementById('push-dedup').value.trim() || 'key-' + Date.now(), synergy_bundle_id: document.getElementById('push-synergy').value.trim() || 'unset', verified: new Date().toISOString().split('T')[0], status: 'active', user_rating: 'unset' }; if (!payload.video_title || !payload.video_url) return showToast('Title and URL required'); try { const res = await fetch('/recommendations/push', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('API error'); } catch (e) { // Offline / Local save data.unshift(payload); localStorage.setItem('taste_recs_db', JSON.stringify(data)); } showToast('Entry pushed'); closeModal('modal-push'); fetchRecs(); } async function submitReview() { const id = document.getElementById('review-id').value; const status = document.getElementById('review-status').value; const rating = document.getElementById('review-rating').value.trim() || 'unset'; const review = document.getElementById('review-notes').value.trim(); const item = data.find(i => String(i.id) === String(id)); if (item) { item.status = status; item.user_rating = rating; item.user_review = review; item.consumed_date = new Date().toISOString().split('T')[0]; } try { await fetch('/recommendations/action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, status, user_rating: rating, user_review: review, consumed_date: item?.consumed_date }) }); } catch (e) { localStorage.setItem('taste_recs_db', JSON.stringify(data)); } showToast('Review committed'); closeModal('modal-review'); updateUI(); } function copyMd(id) { const item = data.find(i => String(i.id) === String(id)); if(!item) return; const md = `| ${item.video_title} | ${item.creator || ''} | ${item.video_url} | ${item.why_this || ''} | ${item.verified || ''} | ${item.status} | ${item.user_rating || 'unset'} | \`${item.dedup_key}\` | ${item.consumed_date || 'unset'} | ${item.synergy_bundle_id || 'unset'} |`; navigator.clipboard.writeText(md); showToast('Markdown copied'); } function exportObsidianMd() { const activeRows = data.filter(d => d.status === 'active').map(item => `| ${item.video_title} | ${item.creator || ''} | ${item.video_url} | ${item.why_this || ''} | ${item.verified || ''} | ${item.status} | ${item.user_rating || 'unset'} | \`${item.dedup_key}\` | ${item.consumed_date || 'unset'} | ${item.synergy_bundle_id || 'unset'} |` ).join(' '); const consumedRows = data.filter(d => d.status === 'consumed').map(item => `| ${item.video_title} | ${item.creator || ''} | ${item.video_url} | ${item.why_this || ''} | ${item.verified || ''} | ${item.status} | ${item.user_rating || 'unset'} | \`${item.dedup_key}\` | ${item.consumed_date || 'unset'} | ${item.synergy_bundle_id || 'unset'} |` ).join(' '); const fullMd = `# ๐ŸŒฟ Taste Map Recommendations โ€” Mahmood ## โšก Active Leaves | video_title | creator | video_url | why_this | verified | status | user_rating | dedup_key | consumed_date | synergy_bundle_id | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | ${activeRows} ## ๐ŸŽฏ Consumed Leaves | video_title | creator | video_url | why_this | verified | status | user_rating | dedup_key | consumed_date | synergy_bundle_id | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | ${consumedRows} `; navigator.clipboard.writeText(fullMd); showToast('Obsidian Markdown exported to clipboard!'); } function copySuperPrompt() { const dbMarkdown = '| Title | Creator | URL | Status | Rating | Review | |---|---|---|---|---|---| ' + data.map(item => `| ${item.video_title} | ${item.creator || ''} | ${item.video_url} | ${item.status.toUpperCase()} | ${item.user_rating || 'unset'} | ${item.user_review || 'none'} |`).join(' '); const superPrompt = `You are an elite content curator and recommendation engine. I am providing you with my complete "Taste Map" (my psychological profile, preferences, and system rules), as well as an export of my current recommendation database. === 1. MY TASTE MAP === ${TASTE_MAP_RESOURCES} === 2. MY CURRENT DATABASE === Here is what I have currently queued, consumed, or rejected. Pay close attention to what I rated highly and what I rejected. ${dbMarkdown} === 3. YOUR TASK === Based strictly on my Taste Map profile and the patterns of what I have Consumed (and avoiding what I have Rejected), please provide 3 brand new, highly-curated YouTube video recommendations that perfectly align with my current intellectual and aesthetic interests. For each recommendation, provide: 1. The Video Title and Creator 2. The URL 3. A sharp, precise explanation of exactly *why* this fits my specific Taste Map and current trajectory.`; navigator.clipboard.writeText(superPrompt); showToast('Mega-Prompt copied to clipboard!'); } function copyVisualLearnSkill() { navigator.clipboard.writeText(VISUAL_LEARN_SKILL); showToast('Visual-Learn Skill copied to clipboard!'); } function escapeHtml(str) { if (!str) return ''; return String(str).replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m])); } function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 3000); } // Init fetchRecs();